import { think } from 'thinkjs';
import { validate, allow, unallow, syncValidate, ValidateRule } from './validate';
import mongo from '../../utils/mongo';
import moment from 'moment';

class CommonModelClass extends think.Model {}

import { ModelRestGetParams } from './rest.type';

interface IObject {
  [propName: string]: any;
}

type RestFormatRuleItemType = 'jsonStringify' | 'jsonParse' | 'dateFormat' | 'dateFormatDateTime';

interface RestFormatRuleItem {
  type: RestFormatRuleItemType;
  /**
   * 重命名作为新增变量否则为替换
   */
  as?: string;
  /** 格式 */
  format?: 'YYYY-MM-DD' | 'MM-DD' | 'YYYY-MM-DD HH:mm' | 'YYYY-MM-DD HH:mm:ss' | string;
}

type RestFormatRule<T extends IObject> = {
  [propName in keyof T]?: ((value: any, source: T) => any) | RestFormatRuleItem | RestFormatRuleItemType;
};

export interface RestOption<T extends IObject> {
  database?: 'mongodb' | 'mysql';
  privateKey: keyof T;
  tableName: string;
  allowEdit?: Array<keyof T>;
  unAllowEdit?: Array<keyof T>;
  autoCreateUpdateTime?: boolean;
  putFormat?: RestFormatRule<T>;
  queryFormat?: RestFormatRule<T>;
  rule?: ValidateRule<T>;
  where?: object | ((params: object) => object | string);
  order?: string;
  alias?: string;
  db?: any;
  join?: (model: CommonModelClass) => CommonModelClass;
  field?: (() => string) | string;
  /**
   * 遍历查询结果
   */
  map?: (item: object, index: number) => object;
}

type FormatFunction = {
  [propName in RestFormatRuleItemType]: (formatRule: RestFormatRuleItem | null, field: string, value: any, source: object) => object;
};
const formatFunction: FormatFunction = {
  jsonStringify: (formatRule, field, value, source) => {
    let json_str = null;
    if (!think.isEmpty(value) && typeof value === 'object') {
      json_str = JSON.stringify(value);
    } else {
      return {};
    }
    return {
      [field]: json_str,
    };
  },
  jsonParse: (formatRule, field, value, source) => {
    console.log('[jsonParse] ', field, value);
    let json_obj = null;
    try {
      json_obj = JSON.parse(value);
    } catch (e) {
      console.log('[e] ', e);
    }
    return {
      [field]: json_obj,
    };
  },
  dateFormat: ({ as, format }, field, value, source) => {
    if (typeof value === 'undefined') return {};
    return {
      [as || field]: moment(value).format(format || 'YYYY-MM-DD'),
    };
  },
  dateFormatDateTime: ({ as, format }, field, value, source) => {
    if (typeof value === 'undefined') return {};
    return {
      [as || field]: moment(value).format(format || 'YYYY-MM-DD HH:mm'),
    };
  },
};
const format = <T extends object>(formatConfig: RestFormatRule<T>, data: T): T => {
  let _data: T = data;
  Object.keys(formatConfig).forEach((field) => {
    if (typeof data !== 'object') return;
    let option = formatConfig[field];
    if (typeof option === 'function') {
      _data[field] = option(data[field], data);
    } else {
      let _type = option;
      if (typeof option === 'object') {
        let { type } = option;
        _type = type;
      }
      if (typeof _type === 'string') {
        let formatFunc = formatFunction[_type];
        if (formatFunc) {
          _data = Object.assign(data, formatFunc(typeof option === 'string' ? { type: option } : option, field, data[field], data));
        }
      }
    }
  });
  return _data;
};

/**
 * 暴露出以供调用
 */
export const autoFormatData = format;

export class RestModelClass<T extends IObject> {
  /**
   * constructor
   */
  constructor(option: RestOption<T>) {
    this.option = option;
  }

  option: RestOption<T> = {
    privateKey: '',
    tableName: '',
  };
  $model: CommonModelClass;

  /**
   * 新增或更新
   */
  async put(params: T) {
    const { privateKey, tableName, allowEdit, unAllowEdit, autoCreateUpdateTime, rule, db, putFormat } = this.option;

    const isAdd = !params[privateKey];
    /**
     * 数据校验
     */
    if (rule) {
      try {
        await syncValidate<T>(params, rule);
      } catch (e) {
        throw new Err(e.message);
      }
    }
    /**
     * 数据过滤
     */
    let data: T = params;
    if (!isAdd) {
      if (allowEdit) data = allow(params, allowEdit as string[]);
      if (unAllowEdit) data = unallow(params, unAllowEdit as string[]);
    }
    /**
     * 数据转换
     */
    if (!think.isEmpty(putFormat)) {
      data = format<T>(putFormat, data);
    }
    const now = Date.now();
    const id = params[privateKey];
    if (think.isEmpty(id)) {
      /** 自动补充时间 */
      if (autoCreateUpdateTime) {
        data = Object.assign(data, {
          create_time: now,
          update_time: now,
        });
      }
    }

    if (this.option.database === 'mongodb') {
      const MongoModel = mongo.model(tableName);
      const newData = new MongoModel(data);
      if (isAdd) {
        let { _id } = await newData.save();
        return _id;
      } else {
        return MongoModel.findByIdAndUpdate(id, data);
      }
    } else {
      let mainModel = think.model(tableName, think.config('model')) as CommonModelClass;
      this.$model = mainModel;
      /** 合并连接 */
      if (db) {
        mainModel = mainModel.db(db);
      }
      if (think.isEmpty(id)) {
        return mainModel.add(data);
      } else {
        const updateRes = await mainModel
          .where({
            [privateKey]: id,
          })
          .update(data);
        if (updateRes !== 1) throw new Err('保存失败');
        return id;
      }
    }
  }

  async formatResult(result: T | T[] | { data: T[] }, params: ModelRestGetParams<T>) {
    const { count } = params;
    const { map, queryFormat } = this.option;
    if (!think.isEmpty(queryFormat)) {
      if (count && !Array.isArray(result) && result.data) {
        // 浅拷贝数据防止出现短时间多次请求同时查询一份数据orm缓存策略导致相互影响，重复格式化出现错误
        result.data = result.data.map((data: T) => format<T>(queryFormat, { ...data }));
      } else if (Array.isArray(result)) {
        // 浅拷贝数据防止出现短时间多次请求同时查询一份数据orm缓存策略导致相互影响，重复格式化出现错误
        result = result.map((data: T) => format<T>(queryFormat, { ...data }));
      } else {
        // 浅拷贝数据防止出现短时间多次请求同时查询一份数据orm缓存策略导致相互影响，重复格式化出现错误
        result = format<T>(queryFormat, { ...result } as T);
      }
    }
    if (map) {
      if (count && !Array.isArray(result) && result.data) {
        for (let i = 0, l = result.data.length; i < l; i++) {
          const item = result.data[i];
          result.data[i] = await map(item, i);
        }
      } else if (Array.isArray(result)) {
        for (let i = 0, l = result.length; i < l; i++) {
          const item = result[i];
          result[i] = (await map(item, i)) as T;
        }
      } else {
        result = (await map(result, 0)) as T;
      }
    }
    return result;
  }

  async get(params: ModelRestGetParams<T>) {
    console.log('[get params] ', params);
    let { currentPage = 1, pageSize = 20, count, filterForm = {} } = params;
    const { database, privateKey, tableName, where, order, join, field, alias, map, queryFormat } = this.option;
    let id = params[privateKey as string];
    // 实现可传入 id | xxx_id 都可查询单个数据
    if (params.id) {
      id = params.id;
    }

    if (database === 'mongodb') {
      const MongoModel = mongo.model(tableName);
      let result = (!!id ? await MongoModel.findOne({ _id: id }) : await MongoModel.find(where)) as any;
      if (map || queryFormat) {
        result = await this.formatResult(result, params);
      }
      return result;
    } else {
      let query = think.model(tableName, think.config('model')) as any;

      let whereSql: string | object = {};
      /**
       * 检索条件
       */
      if (alias) {
        query = query.alias(alias);
      }
      if (join) {
        query = join(query);
      }
      if (where) {
        if (typeof where === 'function') {
          whereSql = JSON.parse(JSON.stringify(where(params)));
        }
        if (typeof where === 'object') {
          whereSql = where;
        }
      }
      // 查询一条数据主动追加主键条件
      if (id) {
        if (typeof whereSql === 'object') {
          whereSql = {
            ...whereSql,
            [(alias ? alias + '.' : '') + privateKey]: id,
          };
        }
      }
      if (!think.isEmpty(whereSql)) {
        query = query.where(whereSql);
      }
      if (field) {
        query = query.field(typeof field === 'function' ? field() : field);
      }
      if (order) {
        query = query.order(order);
      }

      if (id) {
        let result = await query.find();
        if (map || queryFormat) {
          result = await this.formatResult(result, params);
        }
        return result;
      } else {
        query = query.page(currentPage, pageSize);
        let result = count ? await query.countSelect() : await query.select();
        if (map || queryFormat) {
          result = await this.formatResult(result, params);
        }
        return result;
      }
    }
  }

  async delete(params: any) {
    let { id } = params;
    const { privateKey, tableName, database } = this.option;
    id = params[privateKey] || id;
    if (database === 'mongodb') {
      const MongoModel = mongo.model(tableName);
      return MongoModel.findByIdAndRemove(id);
    } else {
      const mainModel = think.model(tableName, think.config('model')) as CommonModelClass;
      return mainModel.where({ [privateKey]: id }).delete();
    }
  }

  async fakeDelete(
    params: {
      [key in keyof T]: number | string;
    } & { delete_user_id?: number },
  ): Promise<'ok'> {
    const { privateKey, tableName, database } = this.option;
    let fakeIsDeleteKey = 'is_display';
    let { id, delete_user_id } = params;
    id = params[privateKey] || id;
    if (database === 'mongodb') {
      const MongoModel = mongo.model(tableName);
      await MongoModel.findByIdAndRemove(id);
      return 'ok';
    } else {
      const mainModel = think.model(tableName, think.config('model')) as CommonModelClass;
      let updateData = {
        delete_at: Date.now(),
        delete_user_id: delete_user_id || 0,
        [fakeIsDeleteKey]: 0,
      };
      const updateRes = await mainModel
        .where({
          [privateKey]: id,
          [fakeIsDeleteKey]: 1,
        })
        .update(updateData);
      if (updateRes !== 1) throw new Err('删除失败');
      return 'ok';
    }
  }
}

export default RestModelClass;
